master
1---
2import { siteConfig } from '@/config';
3import type { BlogPostData } from '@/types/data';
4import ProfileCard from '@components/aside/ProfileCard.astro';
5import TOC from '@components/aside/TOC.astro';
6import MetaList from '@components/widgets/MetaList.astro';
7import MainLayout from '@layouts/MainLayout.astro';
8import { getCategoryUrl, getPostsCount, getSortedPosts, getTagUrl } from '@utils/content-utils';
9import { t } from '@utils/i18n';
10import { Icon } from 'astro-icon/components';
11import dayjs from 'dayjs';
12
13export async function getStaticPaths() {
14 const allBlogPosts = await getSortedPosts();
15 const yearMap = new Map<number, Map<number, { body: string; data: BlogPostData }[]>>();
16 for (const post of allBlogPosts) {
17 const time = dayjs(post.data.published);
18 const year = time.year();
19 const month = time.month() + 1;
20 let monthMap = yearMap.get(year);
21 if (!monthMap) {
22 monthMap = new Map();
23 yearMap.set(year, monthMap);
24 }
25 let monthPosts = monthMap.get(month);
26 if (!monthPosts) {
27 monthPosts = [];
28 monthMap.set(month, monthPosts);
29 }
30 monthPosts.push(post);
31 }
32 const data = Array.from(yearMap.entries()).map(([year, monthMap]) => ({
33 year,
34 data: Array.from(monthMap.entries()).map(([month, postData]) => ({
35 month,
36 data: postData,
37 })),
38 }));
39 const paths = [
40 {
41 params: {
42 time: undefined,
43 },
44 props: {
45 data,
46 },
47 },
48 ...data.map(({ year }) => ({
49 params: {
50 time: year,
51 },
52 props: {
53 data: data,
54 },
55 })),
56 ];
57 return paths;
58}
59
60const { data } = Astro.props;
61const currentYear = Number(Astro.params.time);
62const postCount = await getPostsCount();
63---
64
65<MainLayout>
66 <div class="card border-base-300 bg-base-200 swup-transition-fade border px-6 py-4">
67 <div class="tooltip md:tooltip-right tooltip-bottom mx-auto w-fit">
68 <h1 class="text-center text-3xl font-bold">{t.navigation.archive.title()}</h1>
69 <div class="tooltip-content">
70 {t.status.postsCount(postCount)}
71 </div>
72 </div>
73 {
74 (() => {
75 function renderMonth(year: number, month: number) {
76 let monthData = data
77 .find((d) => d.year === year)
78 ?.data.find((d) => d.month === month)?.data;
79 if (!monthData) {
80 console.log('[WARNING] Month data not found for year:', year, 'month:', month);
81 return <p>SHOULD NOT RENDER THIS, IS A BUG</p>;
82 }
83 return (
84 <ul class="list">
85 {monthData.map(({ data }) => (
86 <li class="list-row">
87 <div class="list-col-grow">
88 <a
89 href={`/posts/${data.slug}`}
90 title={data.title}
91 class="text-lg font-bold"
92 >
93 {data.title}
94 </a>
95 <MetaList
96 class="text-base-content/60 mt-2 items-start text-sm"
97 metas={[
98 {
99 icon: 'material-symbols:category-outline-rounded',
100 text: data.category || t.meta.unCategorized(),
101 link: getCategoryUrl(data.category),
102 },
103 {
104 icon: 'material-symbols:tag-rounded',
105 title: t.meta.tags(),
106 group: data.tags.map((tag) => ({
107 icon: 'material-symbols:tag-rounded',
108 text: tag,
109 link: getTagUrl(tag),
110 })),
111 },
112 ]}
113 />
114 </div>
115 <time
116 datetime={data.published.toISOString()}
117 data-no-relative="true"
118 class="text-base-content/60"
119 >
120 {data.published.toLocaleDateString(siteConfig.lang.replace('_', '-'))}
121 </time>
122 </li>
123 ))}
124 </ul>
125 );
126 }
127
128 function renderYear(year: number, reverse: boolean = false) {
129 const yearData = data.find((d) => d.year === year)?.data;
130 if (!yearData) {
131 console.log('[WARNING] Year data not found for year:', year);
132 return <p>SHOULD NOT RENDER THIS, IS A BUG</p>;
133 }
134 return (
135 <ul class="timeline timeline-snap-icon timeline-vertical max-md:timeline-compact w-full">
136 {yearData.map(({ month }, index) => (
137 <li>
138 {index > 0 && <hr />}
139 <div class="timeline-middle">
140 <Icon
141 name="material-symbols:add-circle-rounded"
142 height="1.25rem"
143 width="1.25rem"
144 />
145 </div>
146 <div
147 class:list={[
148 `timeline-${(index % 2 === 0) !== reverse ? 'start' : 'end'}`,
149 'w-full',
150 ]}
151 >
152 <div
153 class:list={[
154 (index % 2 === 0) !== reverse && 'md:text-end',
155 'mx-4 text-2xl font-bold',
156 ]}
157 id={`${year}-${month}`}
158 >
159 {month}
160 </div>
161 <div class="mx-2">{renderMonth(year, month)}</div>
162 </div>
163 <hr />
164 </li>
165 ))}
166 </ul>
167 );
168 }
169
170 function renderAll() {
171 let totalMonths = 0;
172 return data.map(({ year, data: yearData }) => {
173 const reverse = totalMonths % 2 !== 0;
174 totalMonths += yearData.length;
175 return (
176 <>
177 <div class="divider mt-12 scroll-mt-20 text-2xl font-bold" id={`${year}`}>
178 <a
179 href={`/archives/${year}`}
180 title={`${year}`}
181 class="hover:text-primary duration-200"
182 >
183 {year}
184 </a>
185 </div>
186 <div class="px-4">{renderYear(year, reverse)}</div>
187 </>
188 );
189 });
190 }
191
192 if (currentYear) {
193 return renderYear(currentYear);
194 } else {
195 return renderAll();
196 }
197 })()
198 }
199 </div>
200 <Fragment slot="aside-fixed">
201 <ProfileCard />
202 </Fragment>
203 <Fragment slot="aside-sticky">
204 {
205 currentYear ? (
206 (() => {
207 const yearData = data.find((d) => d.year === currentYear);
208
209 return (
210 yearData && (
211 <TOC
212 headings={yearData.data.map(({ month }) => ({
213 depth: 2,
214 text: month.toString(),
215 slug: `${currentYear}-${month}`,
216 }))}
217 />
218 )
219 );
220 })()
221 ) : (
222 <TOC
223 headings={data.flatMap(({ year, data }) => [
224 {
225 depth: 2,
226 text: year.toString(),
227 slug: `${year}`,
228 },
229 ...data.map(({ month }) => ({
230 depth: 3,
231 text: month.toString(),
232 slug: `${year}-${month}`,
233 })),
234 ])}
235 />
236 )
237 }
238 </Fragment>
239</MainLayout>